/*******************************************************************************
 * Copyright 2018 The MIT Internet Trust Consortium
 *
 * Portions copyright 2011-2013 The MITRE Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package org.mitre.openid.connect.token;

import java.lang.reflect.Array;
import java.util.*;

import com.google.common.base.Joiner;
import com.sun.deploy.util.StringUtils;
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.dto.OrgDto;
import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.repository.impl.JpaUserInfoRepository;
import org.mitre.openid.connect.service.OIDCTokenService;
import org.mitre.openid.connect.service.UserInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Service;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import com.nimbusds.jwt.SignedJWT;

import static org.apache.http.util.TextUtils.isBlank;

@Service
public class ConnectTokenEnhancer implements TokenEnhancer {

	/**
	 * Logger for this class
	 */
	private static final Logger logger = LoggerFactory.getLogger(ConnectTokenEnhancer.class);

	@Autowired
	private ConfigurationPropertiesBean configBean;

	@Autowired
	private JWTSigningAndValidationService jwtService;

	@Autowired
	private ClientDetailsEntityService clientService;

	@Autowired
	private UserInfoService userInfoService;

	@Autowired
	private OIDCTokenService connectTokenService;

	@Autowired //jzl copy from hdb
	private JpaUserInfoRepository jpaUserInfoRepository;

	@Override
	public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,	OAuth2Authentication authentication) {

		OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) accessToken;
		OAuth2Request originalAuthRequest = authentication.getOAuth2Request();

		String clientId = originalAuthRequest.getClientId();
		ClientDetailsEntity client = clientService.loadClientByClientId(clientId);


		//从下面复制
		String usernamex = authentication.getName();
		UserInfo userInfox = userInfoService.getByUsernameAndClientId(usernamex, clientId);

		//hdb add
		client.setUserId(userInfox.getPreferredUsername());
		client.setUserCode(userInfox.getPreferredUsername());
		client.setUserName(userInfox.getName());

		//jzl copy from hdb id_token
		ArrayList<HashMap<String, String>> orgs = jpaUserInfoRepository.getOrgByUserCode(client.getUserId());


		List roleList = jpaUserInfoRepository.getRoleByUserCode(clientId,client.getUserId());
		//add
//		idClaims.claim("AppsStr", isBlank(orgDto.getAppCode()));
//		idClaims.claim("OrgCode", orgDto.getOrgCode());
//		idClaims.claim("OrgId",orgDto.getId() );
//		idClaims.claim("OrgIdPath", orgDto.getOrgIdPath());
//		idClaims.claim("OrgName", orgDto.getOrgName());
//
//		idClaims.claim("UserId", client.getUserId());
//		idClaims.claim("UserCode", client.getUserCode());
//		idClaims.claim("UserName", client.getUserName());
//
//		//临时兼容angular jzl
//		idClaims.claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", client.getUserCode());
//		idClaims.claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", client.getUserName());
//		//1下面角色 暂为空，因为不知道怎么取
//		//2 C# 线上文档系统 这种形式key值应是需要的
//		//3 C# 如果是多个角色 登陆退出 应该会出bug ,
//		//4 若考虑远洋系统： 远洋系统用到了角色， 角色的key是"roles" 且是个数组，
//		idClaims.claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "");
		ArrayList<String> roles=new ArrayList<String>();
		String roleString="";
			if (roleList.size()==0) {
//				roles= "";
			}
//		   else	if (roleList.size()==1) {
//				roles= roleList.get(0).toString();
//				roleString=roleList.get(0).toString();
//			}
			else {
				String[] r = new String[roleList.size()];
				for (int i = 0; i < roleList.size(); i++) {
					roles.add(roleList.get(i).toString());
					roleString=roleString+roleList.get(i).toString()+",";
				}
				roleString.substring(0,roleString.length()-1);
			}

		Builder builder = new JWTClaimsSet.Builder()
				.claim("azp", clientId)
				//copy from id_token jzl
				.claim("Test","Test")
			//	.claim("AppsStr", isBlank(orgs.getAppCode()))
			.claim("AppsStr", true)
			//	.claim("OrgCode", orgDto.getOrgCode())
			//	.claim("OrgId",orgDto.getId() )
			//	.claim("OrgIdPath", orgDto.getOrgIdPath())
			//	.claim("OrgName", orgDto.getOrgName())
			    .claim("Orgs", orgs)
			   .claim("Roles", roleString)
				.claim("UserId", client.getUserId())
				.claim("UserCode", client.getUserCode())
				.claim("UserName", client.getUserName())
				.claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", client.getUserCode())
				.claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", client.getUserName())
				.claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", roles)
				.issuer(configBean.getIssuer())

				.issueTime(new Date())
				.expirationTime(token.getExpiration())
				.subject(authentication.getName())
				.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it

		String audience = (String) authentication.getOAuth2Request().getExtensions().get("aud");
		if (!Strings.isNullOrEmpty(audience)) {
			builder.audience(Lists.newArrayList(audience));
		}

		addCustomAccessTokenClaims(builder, token, authentication);

		JWTClaimsSet claims = builder.build();

		JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
		JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null,
				jwtService.getDefaultSignerKeyId(),
				null, null);
		SignedJWT signed = new SignedJWT(header, claims);

		jwtService.signJwt(signed);

		token.setJwt(signed);

		/**
		 * Authorization request scope MUST include "openid" in OIDC, but access token request
		 * may or may not include the scope parameter. As long as the AuthorizationRequest
		 * has the proper scope, we can consider this a valid OpenID Connect request. Otherwise,
		 * we consider it to be a vanilla OAuth2 request.
		 *
		 * Also, there must be a user authentication involved in the request for it to be considered
		 * OIDC and not OAuth, so we check for that as well.
		 */
		if (originalAuthRequest.getScope().contains(SystemScopeService.OPENID_SCOPE)
				&& !authentication.isClientOnly()) {

			String username = authentication.getName();
			UserInfo userInfo = userInfoService.getByUsernameAndClientId(username, clientId);

			//hdb add
			client.setUserId(userInfo.getPreferredUsername());
			client.setUserCode(userInfo.getPreferredUsername());
			client.setUserName(userInfo.getName());

			if (userInfo != null) {

				JWT idToken = connectTokenService.createIdToken(client,
						originalAuthRequest, claims.getIssueTime(),
					userInfo.getPreferredUsername(), token);

				// attach the id token to the parent access token
				token.setIdToken(idToken);
			} else {
				// can't create an id token if we can't find the user
				logger.warn("Request for ID token when no user is present.");
			}
		}

		return token;
	}

	public ConfigurationPropertiesBean getConfigBean() {
		return configBean;
	}

	public void setConfigBean(ConfigurationPropertiesBean configBean) {
		this.configBean = configBean;
	}

	public JWTSigningAndValidationService getJwtService() {
		return jwtService;
	}

	public void setJwtService(JWTSigningAndValidationService jwtService) {
		this.jwtService = jwtService;
	}

	public ClientDetailsEntityService getClientService() {
		return clientService;
	}

	public void setClientService(ClientDetailsEntityService clientService) {
		this.clientService = clientService;
	}


	/**
	 * Hook for subclasses that allows adding custom claims to the JWT that will be used as access token.
	 * @param builder the builder holding the current claims
	 * @param token the un-enhanced token
	 * @param authentication current authentication
	 */
    protected void addCustomAccessTokenClaims(JWTClaimsSet.Builder builder, OAuth2AccessTokenEntity token,
	    OAuth2Authentication authentication) {
	}

}
